/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Engine2D.engine;

import Engine2D.Game;
import Engine2D.graphics.particle.GLParticle;
import Engine2D.graphics.particle.GLParticleSystem;
import Engine2D.graphics.GLColour;
import Engine2D.math.Vector2i;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL21;

/**
 *
 * @author Garys
 */
public class Engine<E extends Game> implements Runnable {

    /**
     * Singleton instance of the engine
     */
    public static Engine instance = null;
    public boolean running;
    /**
     * The window's width value
     */
    public int w;
    /**
     * The window's height value
     */
    public int h;
    private int depth;
    private E child = null;
    /**
     *
     */
    public int ticks;
    /**
     *
     */
    public int frames;
    private String title;
    private boolean isFullScreen;
    private Vector2i displayBounds;

    private Engine() {
    }

    /**
     *
     * @return
     */
    public static Engine getInstance() {
        if (instance == null) {
            instance = new Engine();
        }

        return instance;
    }

    /**
     *
     */
    public void start() {
        running = true;
        new Thread(this).run();
    }

    @Override
    public void run() {

        long lastTime = System.nanoTime();
        long cycleTime = System.currentTimeMillis();
        double unprocessed = 0;
        double nsPerTick = 1000000000.0 / 60;
        int ticks = 0;
        int frames = 0;



        while (running) {

            long now = System.nanoTime();
            unprocessed += (now - lastTime) / nsPerTick;
            lastTime = now;

            boolean shouldRender = true;

            while (unprocessed > 1) {
                ticks++;
                tick(ticks);
                unprocessed -= 1;
                shouldRender = true;
            }

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (shouldRender) {
                frames++;
                Display.update();

                if (Display.isActive()) {
                    render();
                } else {
                   
                }
                
                 if (Display.isCloseRequested()) {
                        this.stop();
                    }


            }

            if (System.currentTimeMillis() - cycleTime > 1000) {
                System.out.println(ticks + " ticks, " + frames + " frames.");

                this.ticks = ticks;
                this.frames = frames;
                cycleTime += 1000;
                ticks = 0;
                frames = 0;
            }


        }
    }

    /**
     *
     * @param w
     * @param h
     * @param isFullScreen
     * @param child
     * @param title
     */
    public void init(int w, int h, boolean isFullScreen, E child, String title) {
        this.w = w;
        this.h = h;
        this.child = child;
        this.isFullScreen = isFullScreen;
        this.title = title;

        createDisplay();


        child.init();

        // Initialize OpenGL parameters
        initGL(w, h);

    }

    private void initGL(int w, int h) {
        // View aspects
        GL11.glViewport(0, 0, w, h);
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        GL11.glLoadIdentity();
        GL11.glOrtho(0, w, 0, h, -1, 1);


        GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glClearColor(0.0f, 0.7f, 0.0f, 0.5f); // Black Background
        GL11.glClearDepth(1.0f); // Depth Buffer Setup
        // GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables Depth Testing
        GL11.glDepthFunc(GL11.GL_LEQUAL); // The Type Of Depth Testing To Do
        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST); // Really
        // Nice
        // Perspective
        // Calculations
        GL11.glEnable(GL11.GL_TEXTURE_2D);

        // Transparency
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glEnable(GL11.GL_BLEND);
    }

    private void render() {
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
        child.render();
    }

    private void tick(int ticks) {
        child.tick(ticks);



    }

    private void createDisplay() {
        try {

            // Just in case it exists, destroy and reset display mode
            Display.destroy();


            try {
                DisplayMode modes[] = Display.getAvailableDisplayModes();
                int mode = -1;
                for (int i = 0; i < modes.length; i++) {
                    if (modes[i].getWidth() == w
                            && modes[i].getHeight() == h
                            && modes[i].getBitsPerPixel() >= depth) {
                        mode = i;
                        break;
                    }
                }

                if (mode != -1) {
                    System.out.println("Setting display mode to " + modes[mode]);
                    Display.setDisplayMode(modes[mode]);
                }


            } catch (Exception e) {
                System.err.println("Failed to create display mode due to " + e);
            }

            // Now try to create display
            Display.create();
            Display.setTitle(title);
            Display.setFullscreen(isFullScreen);
            Display.setVSyncEnabled(false);

            displayBounds = new Vector2i(Display.getDisplayMode().getWidth(), Display.getDisplayMode().getHeight());

        } catch (LWJGLException ex) {
            Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null, ex);
        }



    }

    /**
     *
     */
    public void destroy() {

        Display.destroy();

        child.onEngineClose();
    }

    /**
     *
     * @return
     */
    public Vector2i getDisplayBounds() {
        return displayBounds;
    }

    /**
     *
     * @return
     */
    public boolean isFullScreen() {
        return isFullScreen;
    }

    private void stop() {
        running = false;
        Display.destroy();
    }

    /**
     *
     * @param x
     * @param y
     * @param w
     * @param h
     * @param colour
     */
    public void drawQuad(int x, int y, int w, int h, GLColour colour) {
        // set the color of the quad (R,G,B,A)
        //GL11.glColor3f(0.5f, 0.5f, 1.0f);

        y = getDisplayBounds().getY() - y;

        colour.set();
        GL11.glEnable(GL11.GL_BLEND);
        //Draw Quad
        GL11.glBegin(GL11.GL_QUADS);
        GL11.glVertex2f(x, y);
        GL11.glVertex2f(x + w, y);
        GL11.glVertex2f(x + w, y - h);
        GL11.glVertex2f(x, y - h);
        GL11.glEnd();

        colour.reset();
    }
}
